﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Xml;
using Common.Utilities;
using Common.Utilities.Dispatcher;
using Workflows.Components.DALC;
using Microsoft.CSharp;

namespace Workflows.Components
{
    /// <summary>
    /// 
    /// </summary>
    public class ForLoopWorker:IWorker 
    {
        #region Implementation of IWorker

        public event WorkerStarted WorkerStartedEvent;
        public event WorkerFinished WorkerFinishedEvent;
        public event WorkerHasError WorkerErredEvent;
        public event WorkerCancelled WorkerCancelledEvent;
        public event WorkerStatusChanged WorkerStatusChangedEvent;

        private Thread workerThread;
        private ForLoopInstruction instruction;
        private ForLoopOutput output;
        private RunContext _Context;
        private Guid _TicketID;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }

        /// <summary></summary>
        public Thread WorkerThread
        {
            get { return workerThread; }
        }
        /// <summary></summary>
        public InstructionBase Instruction
        {
            get { return instruction; }
        }
        /// <summary></summary>
        public ResultBase Output
        {
            get { return output; }
        }
        /// <summary></summary>
        public Type ComponentType
        {
            get { return typeof(ForLoopComponent); }
        }
        /// <summary></summary>
        public RunContext Context
        {
            get { return _Context; }
            set { _Context = value; }
        }
        /// <summary></summary>
        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._TicketID = ticketGuid;
            this.instruction = (ForLoopInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }
        /// <summary></summary>
        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._TicketID = ticketGuid;
            this.instruction = (ForLoopInstruction)request;
            this._Context = context;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }
        /// <summary></summary>
        public void DoWork()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this.instruction);
            }
            this.workerThread = Thread.CurrentThread;
            this.Loop();
        }
        /// <summary></summary>
        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this.instruction);
            }
            this.workerThread = new Thread(new ThreadStart(this.Loop));
            this.workerThread.Start();
        }
        /// <summary></summary>
        public void CancelWork()
        {
            if (this.workerThread != null && this.workerThread.IsAlive)
            {
                this.workerThread.Abort();
                this.workerThread.Join();

                if (this.WorkerCancelledEvent != null)
                {
                    this.WorkerCancelledEvent(this._TicketID, this.GetType(), this.instruction);
                }
            }
        }

        #endregion

        #region thread variables
        private Mutex _SyncObj = new Mutex(false);
        private int _ConcurrentThreadCount = 0;
        private int _MaxConcurrentThreadCount = 4;
        #endregion

        #region work
        private void Loop()
        {
            try
            {
                List<BackgroundWorker> bgWorkers = new List<BackgroundWorker>();
                this._MaxConcurrentThreadCount = int.Parse(ConfigurationManager.AppSettings.Get("JobPool"));
                this._SyncObj = new Mutex(false);
                if (string.IsNullOrEmpty(this.instruction.ConsoleAppFilePath) ||
                    !File.Exists(this.instruction.ConsoleAppFilePath))
                    throw new Exception("Unable to find subflow console application");

                GlobalVariable forVar = this.InitForVar();
                List<GlobalVariable> subVarList =
                    this.ReadGlobalVariablesFromWorkflowConsoleApp(this.instruction.ConsoleAppFilePath);
                while (this.CanContinue(forVar))
                {
                    Dictionary<string, GlobalVariable> gVars = this.instruction.Vars;
                    gVars = this.InitVariables(forVar, gVars, this.instruction.InitCodeBlock);
                    if (this.instruction.SubflowVarBindings != null && this.instruction.SubflowVarBindings.Count > 0)
                    {
                        foreach (string tgtVarName in this.instruction.SubflowVarBindings.Keys)
                        {
                            for (int i = 0; i < subVarList.Count; i++)
                            {
                                if (subVarList[i].VarName == tgtVarName)
                                {
                                    string srcVarName = this.instruction.SubflowVarBindings[tgtVarName];
                                    subVarList[i] = gVars[srcVarName];
                                }
                            }
                        }
                    }

                    if (this.WorkerStatusChangedEvent != null)
                    {
                        string msg = string.Format("Looping {0} of {1}...", forVar.VarValue,
                                                   this.instruction.ForExpression.ToValue);
                        int pct = 10;
                        this.WorkerStatusChangedEvent(this._TicketID, this.GetType(), this.instruction,
                                                      this._WorkflowName, this._ComponentName, msg, pct);
                    }
                    BackgroundWorker bgWorker = this.StartBgWorker(subVarList);
                    while (bgWorker == null)
                    {
                        Thread.Sleep(15000); // wait for 15 sec
                        bgWorker = this.StartBgWorker(subVarList);
                    }
                    bgWorkers.Add(bgWorker);

                    forVar = this.Increment(forVar);
                }

                bool allFinished = true;
                foreach(BackgroundWorker bgWorker in bgWorkers)
                {
                    if(bgWorker !=null && bgWorker.IsBusy)
                    {
                        allFinished = false;
                        break;
                    }
                }
                while(!allFinished)
                {
                    Thread.Sleep(15000);    // wait for 15 sec
                }
                foreach(BackgroundWorker bgWorker in bgWorkers)
                {
                    bgWorker.Dispose();
                }
                this.output = new ForLoopOutput(
                    this.instruction.OwnerComponentID, this._TicketID, this.instruction.Vars);
                if (this.WorkerFinishedEvent != null)
                    this.WorkerFinishedEvent(this._TicketID, this.GetType(), this.instruction, this.output);
            }
            catch (Exception ex)
            {
                if (this.WorkerErredEvent != null)
                    this.WorkerErredEvent(this._TicketID, this.GetType(), this.instruction, ex.ToString(), true);
            }
        }
        #endregion

        #region FOR
        private GlobalVariable InitForVar()
        {
            GlobalVariable forVar=new GlobalVariable(this.instruction.ForExpression.VarName, 
                this.instruction.ForExpression.DataType, this.instruction.ForExpression.FromValue.ToString());
            return forVar;
        }

        private GlobalVariable Increment(GlobalVariable forVar)
        {
            object oldValue = SimpleDataTypeUtil.ToDbValue(forVar.DataType, forVar.VarValue);
            object step = SimpleDataTypeUtil.ToDbValue(forVar.DataType, this.instruction.ForExpression.Step);
            forVar.VarValue = ((int) oldValue + (int) step).ToString();
            return forVar;
        }

        private bool CanContinue(GlobalVariable forVar)
        {
            object varValue = SimpleDataTypeUtil.ToDbValue(forVar.DataType, forVar.VarValue);
            IComparable var1=(IComparable)varValue;
            object toValue = SimpleDataTypeUtil.ToDbValue(forVar.DataType, this.instruction.ForExpression.ToValue);
            IComparable var2=(IComparable)toValue;
            if (var1.CompareTo(var2) <= 0)
                return true;
            else
                return false;
        }
        #endregion

        #region init
        private Dictionary<string,GlobalVariable> InitVariables(GlobalVariable forVar, 
            Dictionary<string, GlobalVariable> gVars, string codeBlock)
        {
            List<string> localAssemblies = new List<string>();
            localAssemblies.Add("Common.Utilities.dll");
            localAssemblies.Add("Workflows.Components.dll");
            CompilerParameters compilerParms = this.AddReferencedAssemblies(null, localAssemblies);
            compilerParms.GenerateInMemory = true;
            CSharpCodeProvider codeProvider = new CSharpCodeProvider();
            CompilerResults compileResult = codeProvider.CompileAssemblyFromSource(compilerParms, codeBlock);
            if (compileResult.Errors.HasErrors)
            {
                throw new Exception(compileResult.Errors[0].ErrorNumber + ": " + compileResult.Errors[0].ErrorText);
            }
            Assembly localAssem = compileResult.CompiledAssembly;
            object instance = localAssem.CreateInstance("Workflows.Components.Initor");
            PropertyInfo gVarProp = instance.GetType().GetProperty("GlobalVariables");
            gVarProp.SetValue(instance, gVars, null);
            PropertyInfo forVarProp = instance.GetType().GetProperty("ForVar");
            forVarProp.SetValue(instance, forVar, null);
            MethodInfo initMethod = instance.GetType().GetMethod("Init");
            initMethod.Invoke(instance, null);
            gVars = (Dictionary<string, GlobalVariable>)gVarProp.GetValue(instance, null);
            return gVars;
        }

        private CompilerParameters AddReferencedAssemblies(
            List<string> globalAssemblies, List<string> localAssemblies)
        {
            CompilerParameters compilerParms = new CompilerParameters();
            if (globalAssemblies == null || !globalAssemblies.Contains("System.dll"))
                compilerParms.ReferencedAssemblies.Add("System.dll");
            if (globalAssemblies == null || !globalAssemblies.Contains("System.Xml.dll"))
                compilerParms.ReferencedAssemblies.Add("System.Xml.dll");
            if (globalAssemblies == null || !globalAssemblies.Contains("System.Data.dll"))
                compilerParms.ReferencedAssemblies.Add("System.Data.dll");
            if (globalAssemblies == null || !globalAssemblies.Contains("System.Drawing.dll"))
                compilerParms.ReferencedAssemblies.Add("System.Drawing.dll");

            if (globalAssemblies != null && globalAssemblies.Count > 0)
            {
                foreach (string globalAssemblyName in globalAssemblies)
                {
                    compilerParms.ReferencedAssemblies.Add(globalAssemblyName);
                }
            }

            string binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            if (localAssemblies != null && localAssemblies.Count > 0)
            {
                foreach (string localAssemblyFileName in localAssemblies)
                {
                    if (File.Exists(localAssemblyFileName))
                    {
                        compilerParms.ReferencedAssemblies.Add(localAssemblyFileName);
                    }
                    else
                    {
                        string filePath = Path.Combine(binFolderPath, Path.GetFileName(localAssemblyFileName));
                        if (File.Exists(filePath))
                            compilerParms.ReferencedAssemblies.Add(filePath);
                    }
                }
            }

            return compilerParms;
        }
        #endregion

        #region read config file
        private List<GlobalVariable> ReadGlobalVariablesFromWorkflowConsoleApp(string consoleAppFilePath)
        {
            List<GlobalVariable> gVars=new List<GlobalVariable>();
            string dirPath = Path.GetDirectoryName(consoleAppFilePath);
            string exeFileName = Path.GetFileNameWithoutExtension(consoleAppFilePath);
            string settingFilePath = Path.Combine(dirPath, exeFileName + ".xml");
            if (File.Exists(settingFilePath))
            {
                XmlDocument xDoc = new XmlDocument();
                xDoc.Load(settingFilePath);
                XmlNodeList varNodes = xDoc.DocumentElement.SelectNodes("GlobalVariables/Variable");
                if(varNodes !=null && varNodes.Count>0)
                {
                    foreach(XmlNode varNode in varNodes)
                    {
                        string varName = XmlDataUtil.GetAttributeValue(varNode, "VariableName", "");
                        SimpleDataType dataType =
                            (SimpleDataType)
                            int.Parse(XmlDataUtil.GetAttributeValue(varNode, "DataType",
                                                                    ((int) SimpleDataType.TextType).ToString()));
                        string varValue = XmlDataUtil.GetAttributeValue(varNode, "VariableValue", "");
                        gVars.Add(new GlobalVariable(varName, dataType, varValue));
                    }
                }
            }
            return gVars;
        }
        #endregion

        #region run console app
        private void RunSubflowConsoleApp(string consoleFilePath, List<GlobalVariable> gVars)
        {
            try
            {
                Process process = new Process();
                ProcessStartInfo startInfo = new ProcessStartInfo();
                startInfo.FileName = consoleFilePath;
                startInfo.WorkingDirectory = Path.GetDirectoryName(consoleFilePath);
                StringBuilder argBuilder = new StringBuilder();
                if (gVars != null && gVars.Count > 0)
                {
                    foreach (GlobalVariable gVar in gVars)
                    {
                        if (argBuilder.Length > 0)
                            argBuilder.Append(" ");
                        argBuilder.Append(string.Format("{0}=\"{1}\"", gVar.VarName, gVar.VarValue));
                    }
                }
                startInfo.Arguments = argBuilder.ToString();
                process.StartInfo = startInfo;
                process.Start();
                process.WaitForExit();
            }
            catch (Exception ex)
            {
                if (this.WorkerErredEvent != null)
                    this.WorkerErredEvent(this._TicketID, this.GetType(), this.instruction, ex.ToString(), false);
            }
        }
        #endregion

        #region bgworker 
        private BackgroundWorker StartBgWorker(List<GlobalVariable> gVars)
        {
            if (this._ConcurrentThreadCount < _MaxConcurrentThreadCount)
            {
                this._SyncObj.WaitOne();
                BackgroundWorker bgWorker = new BackgroundWorker();
                bgWorker.WorkerReportsProgress = true;
                bgWorker.WorkerSupportsCancellation = true;
                bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
                bgWorker.ProgressChanged += new ProgressChangedEventHandler(bgWorker_ProgressChanged);
                bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
                bgWorker.RunWorkerAsync(gVars);
                this._ConcurrentThreadCount += 1;
                this._SyncObj.ReleaseMutex();
                return bgWorker;
            }
            else
            {
                return null;
            }
        }

        void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                List<GlobalVariable> gVars = (List<GlobalVariable>)e.Argument;
                this.RunSubflowConsoleApp(this.instruction.ConsoleAppFilePath, gVars);

                //lock (this._SyncObj)
                //{
                //    if (this._ConcurrentThreadCount > 0)
                //        this._ConcurrentThreadCount -= 1;
                //}
            }
            catch (Exception ex)
            {
                if (this.WorkerErredEvent != null)
                    this.WorkerErredEvent(this._TicketID, this.GetType(), this.instruction, ex.ToString(), false);
                this._SyncObj.WaitOne();
                if (this._ConcurrentThreadCount > 0)
                    this._ConcurrentThreadCount -= 1;
                this._SyncObj.ReleaseMutex();
            }
        }

        void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (this.WorkerStatusChangedEvent != null)
                this.WorkerStatusChangedEvent(this._TicketID, this.GetType(), this.instruction, this._WorkflowName,
                                              this._ComponentName, e.UserState.ToString(), e.ProgressPercentage);
        }

        void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this._SyncObj.WaitOne();
            if (this._ConcurrentThreadCount > 0)
                this._ConcurrentThreadCount -= 1;
            this._SyncObj.ReleaseMutex();
        }

        #endregion
    }

    
}
